-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at:
--         http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.
local class = require 'mc.class'
local log = require 'mc.logging'
local _, lsw_drv = pcall(require, 'lsw_drv')
local component = require 'domain.device.switch.component'
local monitor = require 'domain.debug.monitor'
local defs = require 'domain.defs'
local port = class(component)

local mtr = monitor.get_instance()
local PHY_TYPE = 1
function port:ctor(obj, parent)
    self.obj = obj
    self.parent = parent
    self.run_info = {
        rtk_enable = 0,
        rtk_port_linkStatus = 0,
        rtk_port_speed = 0,
        rtk_port_duplex = 0,
        tx_errors = 0,
        rx_errors = 0,
        tx_pkts = 0,
        rx_pkts = 0
    }
    self.ability = {
        auto_negotiation = 0,
        half_10 = 0,
        full_10 = 0,
        half_100 = 0,
        full_100 = 0,
        fc = 0,
        asy_fc = 0
    }
end

function port:vlan_init()
    if self.obj.PVid == 0xff then
        log:notice('[lsw]port(%d) vlan(%d) not need init', self.obj.PhysicalId, self.obj.PVid)
        mtr:update_state(defs.NetConfigIdx.PortVlanInit, true)
        return
    end
    local res = lsw_drv.l_lsw_port_vlan_init(self.parent:get_type(), self.obj.Unit,
        self.obj.PhysicalId, self.obj.PVid, self.obj.AccFrameType)
    mtr:update_state(defs.NetConfigIdx.PortVlanInit, res)
    if not res then
        log:error('[lsw]port(%s) vlan(%s) init fail', self.obj.PhysicalId, self.obj.PVid)
    else
        log:notice('[lsw]port(%s) vlan(%s) init success', self.obj.PhysicalId, self.obj.PVid)
    end
end

function port:port_init()
    local res = lsw_drv.l_port_init(self.parent:get_type(), self.obj.Unit,
        self.obj.PhysicalId, self.obj.PortType)
    mtr:update_state(defs.NetConfigIdx.PortInit, res)
    if not res then
        error(string.format('[lsw]port(%d) init fail', self.obj.PhysicalId))
    else
        log:notice('[lsw]port(%d) init success', self.obj.PhysicalId)
    end
end

function port:port_iso()
    -- 未配置端口隔离直接返回
    if not self.obj.PortIso or self.obj.PortIso == 0 then
        mtr:update_state(defs.NetConfigIdx.PortIso, true)
        return
    end
    local res = lsw_drv.l_cfg_port_iso(self.parent:get_type(), self.obj.Unit,
        self.obj.PhysicalId, self.obj.PortIso)
    mtr:update_state(defs.NetConfigIdx.PortIso, res)
    if not res then
        log:error('[lsw]port iso(%d) init fail', self.obj.PhysicalId)
    else
        log:notice('[lsw]port iso(%d) init success', self.obj.PhysicalId)
    end
end

function port:port_storm()
    if not self.obj.ExportPort then
        mtr:update_state(defs.NetConfigIdx.PortStorm, true)
        return
    end

    local res = lsw_drv.l_cfg_port_storm(self.parent:get_type(), self.obj.Unit,
        self.obj.PhysicalId)
    mtr:update_state(defs.NetConfigIdx.PortStorm, res)
    if not res then
        log:error('[lsw]port storm(%d) init fail', self.obj.PhysicalId)
    else
        log:notice('[lsw]port storm(%d) init success', self.obj.PhysicalId)
    end
end

function port:start_init()
    self:port_init()
    self:port_iso()
    self:port_storm()
end

function port:port_vlan_init()
    self:vlan_init()
end

function port:set_prop(prop, val)
    if not self.obj[prop] or not val then
        return
    end
    self.obj[prop] = val
end

function port:is_health()
    -- 未配置端口隔离默认状态健康
    if self.obj.PortIso == 0 then
        return true
    end

    local res = lsw_drv.l_lsw_port_isolation_get(self.parent:get_type(), self.obj.Unit, self.obj.PhysicalId)
    if res ~= self.obj.PortIso then
        log:notice("[lsw] port(%s) healthy check failed, res: %s", self.obj.PhysicalId, res)
        return false
    end
    if self.obj.LinkCheck and self.obj.LinkStatus == 0 then
        return false
    end
    return true
end

function port:update_port_status()
    local port_link, port_speed, port_duplex, port_errors = lsw_drv.l_get_port_status(self.parent:get_type(),
        self.obj.Unit, self.obj.PhysicalId, self.obj.PortType, self.obj.ConPhyAddr, PHY_TYPE)
    if not port_link  then
        return
    end

    log:info("update port state:%s, %s, %s, %s", port_link, port_speed, port_duplex, port_errors)
    self:set_prop('LinkStatus', port_link)
    self:set_prop('Speed', port_speed)
    self:set_prop('Duplex', port_duplex)
    self:set_prop('PortError', port_errors)
end

function port:update_port_auto_ngo()
    local auto_nego = lsw_drv.l_lsw_get_switch_port_auto_nego(
        self.parent:get_type(), self.obj.Unit, self.obj.PhysicalId, self.obj.PortType)
    if not auto_nego  then
        return
    end
    log:info("update auto ngo:%s", auto_nego)
    self:set_prop('AutoNego', auto_nego)
end

function port:update_port_stp_state()
    local stp_state = lsw_drv.l_lsw_stp_mstpState_get(
        self.parent:get_type(), self.obj.Unit, self.obj.PhysicalId)
    if not stp_state  then
        return
    end
    log:info("update stp state:%s", stp_state)
    self:set_prop('StpState', stp_state)
end

function port:update_port_pkt_cnt()
    local rxErrors, txErrors, rxPkts, txPkts = lsw_drv.l_rtl_8367_routine_port_pkt_cnt(
        self.parent:get_type(), self.obj.Unit, self.obj.PhysicalId)
    if not rxErrors  then
        return
    end
    log:info("update pkt port:%s, %s, %s, %s", rxErrors, txErrors, rxPkts, txPkts)
    self.run_info.rx_errors = rxErrors
    self.run_info.tx_errors = txErrors
    self.run_info.rx_pkts = rxPkts
    self.run_info.tx_pkts = txPkts
end

function port:update_port_info()
    self:update_port_status()
    self:update_port_auto_ngo()
    self:update_port_stp_state()
    self:update_port_pkt_cnt()
end

function port:get_prop(prop)
    if not self.obj then
        return
    end
    return self.obj[prop]
end

function port:get_port_dynamic_info()
    local physical_id = self:get_prop('PhysicalId')
    local link_status = self:get_prop('LinkStatus')
    local speed = self:get_prop('Speed')
    local duplex = self:get_prop('Duplex')
    local port_err = self:get_prop('PortError')
    local nego = self:get_prop('AutoNego')
    local vid = self:get_prop('PVid')

    return string.format(
        '[ ==== Port(%s):LinkStatus(%s), Speed(%s), Duplex(%s), PortError(%s), AutoNego(%s), PVid(%s),' ..
        'rxErrors(%s), txErrors(%s), rxPkts(%s), txPkts(%s) === ],  ',
        physical_id, link_status, speed, duplex, port_err, nego, vid,
        self.run_info.rx_errors, self.run_info.tx_errors,
        self.run_info.rx_pkts, self.run_info.tx_pkts)
end


return port
